
let hasRoute = false;
let listenId = 0;
const deepClone = (obj) => {  //可传入对象 或 数组
    //  判断是否为 null 或 undefined 直接返回该值即可,
    if (obj === null || !obj) return obj;
    // 判断 是要深拷贝 对象 还是 数组
    if (Object.prototype.toString.call(obj) === "[object Object]") { //对象字符串化的值会为 "[object Object]"
        let target = {}; //生成新的一个对象
        const keys = Object.keys(obj); //取出对象所有的key属性 返回数组 keys = [ ]
        //遍历复制值, 可用 for 循环代替性能较好
        keys.forEach(key => {
            if (obj[key] && typeof obj[key] === "object")
                //如果遇到的值又是 引用类型的 [ ] {} ,得继续深拷贝
                target[key] = deepClone(obj[key]);//递归
            else
                target[key] = obj[key];

        })
        return target  //返回新的对象
    } else if (Array.isArray(obj)) {
        // 数组同理
        let arr = [];
        obj.forEach((item, index) => {
            if (item && typeof item === "object")
                arr[index] = deepClone(item);
            else
                arr[index] = item;
        })
        return arr
    }
}
const isObj = (val) => (val && Object.prototype.toString.call(val)) === "[object Object]";
const isArr = (likeArray) => Array.isArray(likeArray);
const isFunc = (func) => func instanceof Function;
// const getAttr = (num) => {
//     if (typeof num === 'number' || !isNaN(Number(num)))
//         return "[" + num + "]"
//     else
//         return "." + num
// };
// const is_long_type = (target) => {
//     let type = '';
//     if (isObj(target)) type = 'obj'
//     else if (isArr(target)) type = 'arr'
//     return type
// };
//diff算法减少不比较setData
// const diff = (state, newState, maxStack = 0) => {

//     maxStack += 1;
//     if (maxStack > 1000) {
//         throw new TypeError("本次commit数据过大")
//     }
//     // let patch_key = '';
//     // let patch_val = '';
//     let patch = {};
//     let keys;
//     if (isObj(newState)) {
//         keys = Object.keys(newState);
//     } else {
//         keys = newState;
//     }
//     keys.forEach((key, idx) => {
//         isArr(newState) && (key = idx);
//         let ov = state[key];
//         let nv = newState[key];
//         const diff_result= diff_single(ov,nv,key);
//          if(diff_result.key&&diff_result.val){};

//     })
    
// };
// const diff_single = (ov,nv,key) => {
//     key = ["$store" + getAttr(key)]
//     let islong = is_long_type(nv);
//     let patch_key = '';
//     let patch_val = '';
   
//     //如果旧的数据没有,可认为新数据
//     if (!ov) {
//         // return patch[key] = nv;
//          (patch_key += getAttr(key)), (patch_val = nv)
//     }else
//     // 不是引用类型 && 两个类型不相同
//     if (!islong && typeof ov !== typeof nv) {
       
//          (patch_key += getAttr(key)), (patch_val = nv)
//     }else
//     //是对象
//     if (islong && islong === 'obj') {
//         Object.keys()
//         let diff_result = diff_single(ov, nv,key) || {};
//          diff_result.key && diff_result.val && ((patch_key += getAttr(diff_result.key)), (patch_val = diff_result.val));

//     }else
//     //是数组
//     if (islong && islong === 'arr') {
//         let res = diff_single(ov, nv,key) || {};
//          diff_result.key && diff_result.val && ((patch_key += getAttr(diff_result.key)), (patch_val = diff_result.val))

//     }else
//     // 两个值不相等
//     if (nv != ov) {
//         (patch_key += getAttr(key)), (patch_val = nv)
//     }
//     return {key:patch_key,val:patch_val}
// }
// const setData = (data = {}) => {
//     Object.keys(data).forEach((val, key) => {
//         data[`$store[${key}]`] = val;
//         delete data[key]
//     })
//     return data
// }
export class createStore {
    routeType = { "redirectTo": true, "navigateTo": true, "appLaunch": true, "switchTab": true };
    state = {};
    mutations = {};
    events = {};
    oldStore = {};
    static dev = true;
    static global = true;

    constructor(userConfig = {}) {
        const { state, mutations, global = false, dev = true } = userConfig;
        if (!isObj(state)) throw new SyntaxError("创建仓库时,state必须为object");
        if (!isObj(mutations)) throw new SyntaxError("创建仓库时,mutations必须为object");
        this.state = { ...state };
        this.oldStore = deepClone(state);
        this.mutations = { ...mutations };
        createStore.global = global;
        createStore.dev = dev;
        // 防止多次注册函数
        if (!hasRoute) {
            wx.onAppRoute(({ openType, webviewId }) => { this.routeType[openType] && this.updateData() });
            hasRoute = true;
        }

    }

    $commit = (event, val) => {
        if (!val && createStore.dev) console.log(`%c 🍇 事件${event}commit内容为空: `, 'font-size:20px;background-color: #7F2B82;color:#fff;', val);;
        if (!event && createStore.dev) return console.error(`commit:${event} 不能为空`);
        if (!this.mutations[event] && createStore.dev) return console.log(`%c 🍓 找不到 ${event} commit方法: 在store中未找到匹配`, 'font-size:20px;background-color: #FFDD4D;color:#fff;', event);
        if (!isFunc(this.mutations[event]) && createStore.dev) return console.log(`%c 🍇 事件${event} 不是函数: 请在store中检查`, 'font-size:20px;background-color: #7F2B82;color:#fff;',);;
        // 如果传进来是个对象,就要按小程序的模式进行 更新数据
        if (isObj(event)) {
            // 同步库
            Object.keys(event).forEach((key,idx)=>{
                this.state[key] = event[key];
            })
        } else {
            // 保存旧的store
            this.oldStore = deepClone(this.state);
            // 调用对应的mutation   
            this.mutations[event](this.state, val);
        }
        this.updateData('commit');

    }

    // 更新数据
    updateData = () => {
        // 这里有待优化, 要对页面声明依赖的数据进行对比,如果发生变化就需要进行更新数据
        getCurrentPages().forEach(pageContext => {
            let stateMap = {};
            if (createStore.global) {
                if (isArr(pageContext.useStore)) {
                    pageContext.useStore.forEach(key => stateMap[key] = this.state[key]);
                } else if (pageContext.useStore !== false) { // 如果设置了useStore=false 则没必要渲染
                    stateMap = { ...this.state }
                }
            } else {
                if (isArr(pageContext.useStore)) {
                    pageContext.useStore.forEach(key => stateMap[key] = this.state[key]);
                } else if (!!pageContext.useStore === true) {
                    stateMap = { ...this.state }
                } else {
                    createStore.dev && Object.keys(stateMap).length <= 0 && console.error(`当前页:${pageContext.route} 未设置useStore(未设置global为true,需配置useStore)`)
                }
            }
            Object.keys(stateMap).length > 0 && pageContext.setData({$store:stateMap});

        });
    }

    $listen = (eventName, callback) => {
        if (!eventName) throw new TypeError("$listen 第一个参数:事件名为空");
        if (!callback) throw new TypeError("listen 第二个参数:回调函数为空");
        let currentPage = getCurrentPages().slice(-1)[0];
        if (!isFunc(callback)) return console.error('$listen 第二个参数必须是 函数');
        if (!this.events[eventName]) this.events[eventName] = [];
        // 这里防止$listen放在onShow里,被多次触发,会放个重复的callback
        if (this.events[eventName].find((cb) => cb.eventName == eventName && cb.ctx === currentPage)) return;
        callback.listenId = listenId += 1;
        callback.ctx = currentPage;
        callback.eventName = eventName;
        this.events[eventName].push(callback);
        return listenId;
    }
    $emit = (eventName, params) => {
        if (!eventName) return console.error('$emit 请传入正确事件值')
        let targetFunc = this.events[eventName] || [];
        let currentPages = getCurrentPages().slice(-1)[0];
        for (let index = 0; index < targetFunc.length; index++) {
            const cb = targetFunc[index];
            if (cb === null) {
                targetFunc.splice(index, 1);
                index--;
                continue
            }
            isFunc(cb) && cb(params, currentPages)
        }
    }
    $offListen = (eventName, callbackId) => {
        if (!eventName) throw new TypeError("$offListen 第一个参数:事件名为空");

        let targetFunc = this.events[eventName] || [];
        if (targetFunc.length <= 0) return;
        isArr(targetFunc) && targetFunc.forEach((cb, idx) => {
            if (!callbackId) {
                this.events[eventName][idx] = null;
            } else
                if (cb.listenId == callbackId) {
                    this.events[eventName][idx] = null;
                }
        })
    }
}


// 文档

// 1. 新建个  store.js  内容如下
/*import { createStore } from './store-core';
export const store = new createStore({
    state: { // 这里是仓库值
        author:'rick',
        arr:[]
    },
    dev:false,//打开会有自动 错误提示
    global:false,  //是否开启自动全局注入state, 开启全局后页面多了会影响性能,二十个页面内性能正常
    mutations: {// 这里参考vuex
        changeAuthor(state){
            state.author  = 'nancy';
        },
        s(state,val){
            state.arr = val
        }
    }
})*/

//  2. 在app.js 中引入 store.js
/*      import { store } from './store'
        App({
            store,
            data: {}
        })
 */


 //3. 在每个页面   this.data.$store   即可访问到仓库的东西, 

    // global关闭状态 =====>>> 页面按需引入 要声明 useStore:['仓库成员变量名','仓库成员变量名'] 
    //                        全部变量注入   只要声明 useStore:true 
    // !!!!!!!! global=false 情况下不声明useStore,将获取不到仓库值

    //global开启状态 ======>>>>  
    //    自动每个页面可以访问到, 如按需加载可参考上面

 //4. 如何修改仓库值 ,
//       方式1    getApp().store.$commit("muataion事件名",值);
//       方式2    getApp().store.$commit({ author:'nancy'   });   用小程序setData方式


 //额外功能: 仓库提供监听功能  
 // 1.使用 :  $listen("事件名",(params)=>{})  事件名任意,触发的时候按事件名进行触发 params来自$emit传的第二个参数
 //  $listen 函数会返回一个 listenId 标记此次监听的id,在解绑监听事用,
 //   多个页面可以同时监听同一个事件, 同页面无法同时监听一个事件 
 // 注:  事件名 由你自己定, 大概理念请参考组件传值的监听事件, 钱

 // 2.触发:  $emit("事件名",这里可传参) 
 //    会触发 $listen()监听过的事件,事件名来区分

 // 3.解绑: $offListen("事件名",listenId)
 //    如有传 listenId 只会解绑一个监听
//      如果没传会对  事件名下 所有回调,接触绑定

//例如
// 个人中心页: let listenId= store.$listen('login',(msg)=>{ msg:登录了 //监听到登录了,这里开始获取用户信息 })
// 取消监听 :  store.$offListen("login",listenId) ; // 这里取消监个人中心,不影响详情页的
// 同名,不同页面可同时一起监听
// 详情页 : let listenId= store.$listen('login',(msg)=>{ msg:登录了 //这里也监听到登录了,  })

//登录页: store.$emit('login','登录了') //这里触发监听















